package com.lly.ui

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animate
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PauseCircle
import androidx.compose.material.icons.filled.PlayCircle
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.center
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.rotate
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.center
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlin.random.Random

@Composable
fun BaseDial(
    modifier: Modifier,
    dial: DialPlayer,
    bgColors: Array<Color>,
    textStyle: TextStyle,
    pointerColor: Color,
    textPaddingTop: Dp = 2.dp,
    enableWatchLine: Boolean = false,
) {
    check(dial.data.isNotEmpty()) { "RandomDial can not has empty data " }
    check(bgColors.isNotEmpty()) { "RandomDial can not has empty bgColors " }
    val textMeasurer = rememberTextMeasurer()
    Canvas(modifier = modifier.graphicsLayer {
        rotationZ = dial.rotateZ.value
        rotationX = dial.rotateX.value
        rotationY = dial.rotateX.value
        clip = false
    }) {
        with(drawContext) {
            val constraints =
                Constraints(
                    maxWidth = (textStyle.fontSize.value
                            * density.density
                            ).toInt()
                )
            val previousSize = size
            val fontDensity = Density(density.density)
            canvas.save()
            repeat(dial.count) { i ->
                drawArc(bgColors[i % bgColors.size], dial.startAngle, dial.unit, true)
                if (enableWatchLine) {
                    drawLine(textStyle.color.copy(alpha = 0.2f), center.copy(y = 0f), center)
                }
                val measure = textMeasurer.measure(
                    dial.data[i],
                    textStyle,
                    constraints = constraints,
                    overflow = TextOverflow.Visible,
                    density = fontDensity
                )
                withTransform({
                    translate(center.x - measure.size.center.x, textPaddingTop.value)
                }) {
                    measure.multiParagraph.paint(canvas = drawContext.canvas)
                }

                canvas.rotate(dial.unit, size.center.x, size.center.y)
            }
            canvas.restore()
            size = previousSize
        }
    }
    val pointSeepAngle = 360f / 30
    val pointerStartAngle = 360 - pointSeepAngle / 2 + 90
    Canvas(modifier = modifier) {
        withTransform({
            scale(0.6f, center)
            translate(top = -center.y)
        }) {
            drawArc(pointerColor, pointerStartAngle, pointSeepAngle, true)
        }
        drawCircle(pointerColor, pointSeepAngle * 2, blendMode = BlendMode.Src)
    }
}

class DialPlayer(
    val data: List<String>,
    initId: Int = 0,
    private val xAxis: Float = 0f,
    private val defaultTurnNumber: Int = 15,
    private val animationSpec: AnimationSpec<Float> = spring(
        dampingRatio = Spring.DampingRatioHighBouncy,
        stiffness = Spring.StiffnessLow
    ), private val onFinish: (Int) -> Unit
) {
    val count = data.size
    val unit = 360f / count
    val startAngle = 360 - 90 - unit / 2
    val rotateZ = mutableFloatStateOf(360 - initId * startAngle)
    val rotateX = mutableFloatStateOf(0f)
    val idle = mutableStateOf(true)
    private var once = false
    private var job: Job? = null
    fun play(scope: CoroutineScope, targetId: Int) {
        job?.cancel()
        idle.value = false
        job = scope.launch {
            var xJob: Job? = null
            if (xAxis > 0) {
                xJob = launch {
                    animate(
                        -xAxis,
                        xAxis,
                        animationSpec = infiniteRepeatable(
                            tween(90),
                            RepeatMode.Reverse
                        )
                    ) { v, _ ->
                        rotateX.value = v
                    }
                }
            }
            val targetValue = 360 * defaultTurnNumber - unit * targetId
            animate(
                rotateZ.value,
                targetValue,
                animationSpec = animationSpec
            ) { v, _ ->
                rotateZ.value = v % 360
                if (v == targetValue && !once) {
                    once = true
                    xJob?.let {
                        it.cancel()
                        scope.launch {
                            animate(
                                rotateX.value, 0f, animationSpec = spring(
                                    dampingRatio = Spring.DampingRatioLowBouncy,
                                    stiffness = Spring.StiffnessMediumLow
                                )
                            ) { v, _ ->
                                rotateX.value = v
                            }
                        }

                    }
                }
            }
            once = false
            idle.value = true
            onFinish(targetId)
            job?.cancel()
        }
    }

    fun cancel() {
        job?.cancel()
        idle.value = true
    }

}


@Composable
fun Dial(
    dial: DialPlayer,
    modifier: Modifier = Modifier
        .size(300.dp, 300.dp)
        .background(Color.Black)
        .padding(10.dp), getRandomId: () -> Int
) {
    Box(
        contentAlignment = Alignment.Center,
        modifier = modifier
    ) {
        val scope = rememberCoroutineScope()
        BaseDial(
            modifier = Modifier
                .matchParentSize(),
            dial,
            bgColors = arrayOf(
                Color(0xFF1FE627), Color(0xFF4CAF50),
                Color(0xFF8BC34A), Color(0xFFCDDC39)
            ),
            textStyle = TextStyle.Default.copy(fontSize = 10.sp, color = Color.Black),
            pointerColor = Color.Black.copy(alpha = 0.5f),
            enableWatchLine = false,
        )
        AnimatedContent(
            targetState = dial.idle.value, label = "dialButton",
        ) { idle ->
            IconButton(
                onClick = {
                    if (idle) {
                        dial.play(scope, getRandomId())
                    } else {
                        dial.cancel()
                    }
                }) {
                Icon(
                    modifier = Modifier.size(30.dp),
                    imageVector = if (idle) Icons.Default.PlayCircle else Icons.Default.PauseCircle,
                    contentDescription = "play dial",
                    tint = Color.Green
                )
            }
        }
    }
}

@Preview(fontScale = 2f)
@Composable
fun PreviewRandomDial() {
    var targetId by remember {
        mutableStateOf(0)
    }
    val dial = remember {
        DialPlayer(data = (1..64).map { it.toString() }, initId = targetId, xAxis = 5f) {}
    }
    Dial(dial) {
        Random(System.currentTimeMillis()).nextInt(0, dial.count)
    }

}
